﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlServerCe;
using System.Reflection;
using Shared;

namespace Tui
{
    internal static class ObjectReaderWriter
    {
        private static Dictionary<Key, Delegate> _cacheOfObjectPopulators = new Dictionary<Key, Delegate>(1);
        private static Dictionary<Key, object> _cacheOfResultsetPopulators = new Dictionary<Key, object>(1);

        private struct Key
        {
            public string TableName { get; set; }
            public Type Type { get; set; }
            public override int GetHashCode() { return TableName.GetHashCode() ^ Type.GetHashCode(); }
            public override bool Equals(object obj)
            {
                if (!(obj is Key)) return false;
                Key other = (Key)obj;
                return this.Type.Equals(other.Type) && this.TableName.Equals(other.TableName);
            }
        }

        private static Key GetKey<T>(SqlCeDataReader reader)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = reader.FieldCount - 1; i >= 0; i--) sb.Append(reader.GetName(i)).Append(':');
            return new Key() { TableName = sb.ToString(), Type = typeof(T) };
        }

        internal static Action<SqlCeDataReader, T> GetTransferer<T>(SqlCeDataReader reader)
        {
            Key key = GetKey<T>(reader);

            Delegate d;
            if (!_cacheOfObjectPopulators.TryGetValue(key, out d))
            {
                d = CreateTransferer<T>(reader);
                var newCache = new Dictionary<Key, Delegate>(_cacheOfObjectPopulators);
                newCache[key] = d;
                System.Threading.Interlocked.Exchange(ref _cacheOfObjectPopulators, newCache);
            }

            return (Action<SqlCeDataReader, T>)d;
        }
        internal static Action<SqlCeDataReader, T> CreateTransferer<T>(SqlCeDataReader reader)
        {
            var transferPoints =
                System.Linq.Enumerable.Range(0, reader.FieldCount)
                .Select(i => new { Ord = i, ColName = reader.GetName(i).Replace(" ", ""), ColType = reader.GetFieldType(i) })
                .Join(typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(p => p.CanWrite),
                    dr => dr.ColName,
                    p => p.Name,
                    (dr, p) => new { Prop = p, ColOrd = dr.Ord, ColName = dr.ColName, ColType = dr.ColType },
                    StringComparer.OrdinalIgnoreCase);

            var list = new List<ObjectWriter<T>>();

            foreach (var t in transferPoints)
            {
                int ord = t.ColOrd;
                MethodInfo setMethod = t.Prop.GetSetMethod();
                Type pt = t.Prop.PropertyType;

                if (pt == typeof(string))
                {
                    var ow = new ObjectWriterString<T>();
                    list.Add(ow.Setup(ord, setMethod));
                }
                else if (pt == typeof(Int32))
                {
                    var ow = new ObjectWriterInt32<T>();
                    list.Add(ow.Setup(ord, setMethod));
                }
                else if (pt == typeof(bool))
                {
                    var ow = new ObjectWriterBool<T>();
                    list.Add(ow.Setup(ord, setMethod));
                }
                else if (pt == typeof(DateTime))
                {
                    var ow = new ObjectWriterDate<T>();
                    list.Add(ow.Setup(ord, setMethod));
                }
                else
                {
                    if (pt == typeof(DateTime?)) AddNullStruct<T, DateTime>(list, ord, setMethod);
                    else if (pt == typeof(Int32?)) AddNullStruct<T, Int32>(list, ord, setMethod);
                    else if (pt == typeof(bool?)) AddNullStruct<T, bool>(list, ord, setMethod);
                    else if (pt == typeof(Int16?)) AddNullStruct<T, Int16>(list, ord, setMethod);
                    else if (pt == typeof(Int64)) AddStruct<T, Int64>(list, ord, setMethod);
                    else if (pt == typeof(Int64?)) AddNullStruct<T, Int64>(list, ord, setMethod);
                    else if (pt == typeof(Guid)) AddStruct<T, Guid>(list, ord, setMethod);
                    else if (pt == typeof(Guid?)) AddNullStruct<T, Guid>(list, ord, setMethod);
                    else if (pt == typeof(decimal)) AddStruct<T, decimal>(list, ord, setMethod);
                    else if (pt == typeof(decimal?)) AddNullStruct<T, decimal>(list, ord, setMethod);
                    else if (pt == typeof(double)) AddStruct<T, double>(list, ord, setMethod);
                    else if (pt == typeof(double?)) AddNullStruct<T, double>(list, ord, setMethod);
                    else if (pt == typeof(byte)) AddStruct<T, byte>(list, ord, setMethod);
                    else if (pt == typeof(byte?)) AddNullStruct<T, byte>(list, ord, setMethod);
                    else
                        throw new NotImplementedException("Not Implemented Yet.");
                }
            }
            var l2 = list.ToArray();
            return delegate(SqlCeDataReader dr, T o) { for (int i = 0; i < l2.Length; i++) l2[i].SetObject(dr, o); };
        }

        private static void AddNullStruct<T, TS>(List<ObjectWriter<T>> list, int ord, MethodInfo setMethod) where TS : struct
        {
            list.Add((new ObjectWriterStructNull<T, TS>()).Setup(ord, setMethod));
        }

        private static void AddStruct<T, TS>(List<ObjectWriter<T>> list, int ord, MethodInfo setMethod) where TS : struct
        {
            list.Add((new ObjectWriterStruct<T, TS>()).Setup(ord, setMethod));
        }

        //        private static void AddNullStruct<T,TS>(
        private class ObjectWriterStruct<T, TS> : ObjectWriter<T, TS> where TS : struct { public override void SetObject(SqlCeDataReader dr, T t) { _objectPopulator(t, (TS)dr[Ordinal]); } }
        private class ObjectWriterStructNull<T, TS> : ObjectWriter<T, Nullable<TS>> where TS : struct { public override void SetObject(SqlCeDataReader dr, T t) { _objectPopulator(t, dr[Ordinal] as Nullable<TS>); } }
        private class ObjectWriterString<T> : ObjectWriter<T, string> { public override void SetObject(SqlCeDataReader dr, T t) { _objectPopulator(t, dr[Ordinal].ToString()); } }
        private class ObjectWriterInt32<T> : ObjectWriter<T, int> { public override void SetObject(SqlCeDataReader dr, T t) { _objectPopulator(t, dr.GetInt32(Ordinal)); } }
        private class ObjectWriterBool<T> : ObjectWriter<T, bool> { public override void SetObject(SqlCeDataReader dr, T t) { _objectPopulator(t, dr.GetBoolean(Ordinal)); } }
        private class ObjectWriterDate<T> : ObjectWriter<T, DateTime> { public override void SetObject(SqlCeDataReader dr, T t) { _objectPopulator(t, dr.GetDateTime(Ordinal)); } }


        private abstract class ObjectWriter<T>
        {
            public abstract void SetObject(SqlCeDataReader dr, T t);
        }
        private abstract class ObjectWriter<T, TV> : ObjectWriter<T>
        {
            protected int Ordinal { get; private set; }
            protected Action<T, TV> _objectPopulator;
            public ObjectWriter<T, TV> Setup(int ordinal, MethodInfo meth)
            {
                _objectPopulator = (Action<T, TV>)Delegate.CreateDelegate(typeof(Action<T, TV>), null, meth);
                Ordinal = ordinal;
                return this;
            }
        }

        internal abstract class ResultsetWriter<T>
            where T : BusinessObject<T>, new()
        {
            public abstract void WriteToResultset(SqlCeResultSet dr, T t);
            public abstract void WriteToResultset(SqlCeUpdatableRecord dr, T t);
            public bool IsPrimaryKey { get; protected set; }
            internal abstract object ReadValue(T t);
        }
        private class ResultsetWriter<T, TV> : ResultsetWriter<T>
            where T : BusinessObject<T>, new()
        {
            protected int Ordinal { get; private set; }
            protected Func<T, TV> _objectPopulator;
            public ResultsetWriter<T, TV> Setup(int ordinal, MethodInfo meth, bool isPrimaryKey)
            {
                _objectPopulator = (Func<T, TV>)Delegate.CreateDelegate(typeof(Func<T, TV>), null, meth);
                Ordinal = ordinal;
                IsPrimaryKey = isPrimaryKey;
                return this;
            }

            internal override object ReadValue(T t) { return _objectPopulator(t); }
            public override void WriteToResultset(SqlCeResultSet dr, T t)
            {
                object ts = ReadValue(t);
                dr.SetValue(Ordinal, ts ?? DBNull.Value);
            }
            public override void WriteToResultset(SqlCeUpdatableRecord dr, T t)
            {
                object ts = ReadValue(t);
                dr.SetValue(Ordinal, ts ?? DBNull.Value);
            }
        }







        internal static PopulateResultset<T> GetPopulateResultsetThing<T>(SqlCeConnection cnn, SqlCeDataReader reader, BusinessObjectSupport bos)
            where T : BusinessObject<T>, new()
        {
            Key key = GetKey<T>(reader);

            object d;
            if (!_cacheOfResultsetPopulators.TryGetValue(key, out d))
            {
                string[] pkns = DatabaseInfo.GetPrimaryKeyColumnNames(cnn, bos.TableName);
                d = CreatePopulateResultset<T>(reader, pkns);
                var newCache = new Dictionary<Key, object>(_cacheOfResultsetPopulators);
                newCache[key] = d;
                System.Threading.Interlocked.Exchange(ref _cacheOfResultsetPopulators, newCache);
            }

            return (PopulateResultset<T>)d;
        }
        private static PopulateResultset<T> CreatePopulateResultset<T>(SqlCeDataReader reader, string[] pKnames)
            where T : BusinessObject<T>, new()
        {
            var properties = typeof(T)/*.GetGenericArguments()[0]*/.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(p => p.CanRead);
            var transferPoints =
                System.Linq.Enumerable.Range(0, reader.FieldCount)
                .Select(i => new { Ord = i, ColName = reader.GetName(i).Replace(" ", ""), ColType = reader.GetFieldType(i) })
                .Join(properties,
                    dr => dr.ColName,
                    p => p.Name,
                    (dr, p) => new { Prop = p, ColOrd = dr.Ord, ColName = dr.ColName, ColType = dr.ColType },
                    StringComparer.OrdinalIgnoreCase);

            var list = new List<ResultsetWriter<T>>();

            var pKnames2 = pKnames.Select(t => t.Replace(" ", "")).ToDictionary(t => t);

            foreach (var t in transferPoints)
            {
                int ord = t.ColOrd;
                MethodInfo getMethod = t.Prop.GetGetMethod();
                Type pt = t.Prop.PropertyType;
                bool isPk = pKnames2.ContainsKey(t.ColName);

                if (pt == typeof(string)) AddPR<T, string>(list, ord, getMethod, isPk);
                else if (pt == typeof(bool)) AddPR<T, bool>(list, ord, getMethod, isPk);
                else if (pt == typeof(bool?)) AddPR<T, bool?>(list, ord, getMethod, isPk);
                else if (pt == typeof(byte)) AddPR<T, byte>(list, ord, getMethod, isPk);
                else if (pt == typeof(byte?)) AddPR<T, byte?>(list, ord, getMethod, isPk);
                else if (pt == typeof(Int32)) AddPR<T, Int32>(list, ord, getMethod, isPk);
                else if (pt == typeof(Int32?)) AddPR<T, Int32?>(list, ord, getMethod, isPk);
                else if (pt == typeof(Int16)) AddPR<T, Int16>(list, ord, getMethod, isPk);
                else if (pt == typeof(Int16?)) AddPR<T, Int16?>(list, ord, getMethod, isPk);
                else if (pt == typeof(Int64)) AddPR<T, Int64>(list, ord, getMethod, isPk);
                else if (pt == typeof(Int64?)) AddPR<T, Int64?>(list, ord, getMethod, isPk);
                else if (pt == typeof(DateTime)) AddPR<T, DateTime>(list, ord, getMethod, isPk);
                else if (pt == typeof(DateTime?)) AddPR<T, DateTime?>(list, ord, getMethod, isPk);
                else if (pt == typeof(decimal)) AddPR<T, decimal>(list, ord, getMethod, isPk);
                else if (pt == typeof(decimal?)) AddPR<T, decimal?>(list, ord, getMethod, isPk);
                else if (pt == typeof(double)) AddPR<T, double>(list, ord, getMethod, isPk);
                else if (pt == typeof(double?)) AddPR<T, double?>(list, ord, getMethod, isPk);
                else if (pt == typeof(Guid)) AddPR<T, Guid>(list, ord, getMethod, isPk);
                else if (pt == typeof(Guid?)) AddPR<T, Guid?>(list, ord, getMethod, isPk);
                else
                    throw new NotImplementedException("Not Implemented Yet.");
            }
            return new PopulateResultset<T>(list.ToArray());
        }

        private static void AddPR<T, TS>(List<ResultsetWriter<T>> list, int ord, MethodInfo getMethod, bool isPrimaryKey)
            where T : BusinessObject<T>, new()
        {
            list.Add((new ResultsetWriter<T, TS>()).Setup(ord, getMethod, isPrimaryKey));
        }




        internal class PopulateResultset<T2> where T2 : BusinessObject<T2>, new()
        {
            private readonly ResultsetWriter<T2>[] _rsw;
            public PopulateResultset(ResultsetWriter<T2>[] rsw) { _rsw = rsw; }

            public object[] CreatePrimaryKeyArray()
            {
                int c = 0;
                for (int i = 0; i < _rsw.Length; i++) if (_rsw[i].IsPrimaryKey) c++;
                return new object[c];
            }
            public void LoadPrimaryKey(BusinessObject<T2> t, object[] pk)
            {
                int k = 0;
                int i = 0;
                while (k < pk.Length) if (_rsw[i].IsPrimaryKey) pk[k++] = _rsw[i++].ReadValue(t.TItem);
            }

            public void WriteToResultset(SqlCeResultSet dr, BusinessObject<T2> t)
            {
                ResultsetWriter<T2>[] rsw = _rsw;
                for (int i = 0; i < rsw.Length; i++)
                {
                    var rswx = rsw[i];
                    if (!rswx.IsPrimaryKey) rsw[i].WriteToResultset(dr, t.TItem);
                }
            }

            public void WriteToNewRecord(SqlCeUpdatableRecord dr, BusinessObject<T2> t)
            {
                ResultsetWriter<T2>[] rsw = _rsw;
                for (int i = 0; i < rsw.Length; i++) rsw[i].WriteToResultset(dr, t.TItem);
            }


        }

    }
}
